#include <windows.h>
#include <GL/gl.h>
#include "Camera.h"
#include "Game.h"
#include "Entity.h"
#include "Transformation.h"
#include "Player.h"

#include "Scene.h"
#include <gl/GLU.h>
#include <glut.h>

Camera::Camera( int width, int height, Game* game )
{
	m_game = game;
	m_width = width;
	m_height = height;

	m_RenderVisitor = new RenderVisitor(this);

	m_cameraPos.x = 0;
	m_cameraPos.y = 10;
	m_cameraPos.z = 150;
	m_cameraAngle.y = 180;

	resize( width, height );
	SDL_WM_SetCaption("Sario!", NULL);  

	/*glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);*/
}

Camera::~Camera()
{
	delete m_RenderVisitor;
}

void Camera::resize( int width, int height )
{
	m_width = width;
	m_height = height;

	m_screen = SDL_SetVideoMode(width, height, 0, SDL_INIT_AUDIO|SDL_OPENGL );

	glViewport(0,0,width,height);                       

	glMatrixMode(GL_PROJECTION);        
	glLoadIdentity();                                   

	gluPerspective(90.0f,(GLfloat)width/(GLfloat)height, 1.0f,10000000.0f);  
	glClearColor(0.15, 0.15, 0.3, 0.0);

	SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 );    
	SDL_GL_SetAttribute( SDL_GL_STENCIL_SIZE, 0);
	SDL_GL_SetAttribute( SDL_GL_ACCUM_RED_SIZE, 0);
	SDL_GL_SetAttribute( SDL_GL_ACCUM_GREEN_SIZE, 0);
	SDL_GL_SetAttribute( SDL_GL_ACCUM_BLUE_SIZE, 0);
	SDL_GL_SetAttribute( SDL_GL_ACCUM_ALPHA_SIZE, 0);

	glShadeModel(GL_SMOOTH); // Type of shading for the polygons

	glEnable(GL_DEPTH_TEST); // We enable the depth test (also called z buffer)
	glDepthMask(GL_TRUE);
	glEnable(GL_CULL_FACE);

	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glEnable(GL_LINE_SMOOTH);
	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);	// Really Nice Perspective Calculations

	TextureLoader::getInstance().reload();
}

void Camera::render( float frametime )
{
	glMatrixMode(GL_PROJECTION);						// Select The Projection Matrix
	glLoadIdentity();									// Reset The Projection Matrix
	gluPerspective(90.0f,(GLfloat)m_width/(GLfloat)m_height,1.0f,10000000.0f);		// Recreate The Frustum
	glMatrixMode(GL_MODELVIEW);		

	m_RenderVisitor->reset();

	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // This clear the background color to dark blue

	glMatrixMode(GL_MODELVIEW); // Modeling transformation
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glLoadIdentity(); // Initialize the model matrix as identity
	GLfloat  LightPosition[] = { 0.0f, 10.0f, 0.0f, 1.0f };
	GLfloat light_ambient[]= { 1.0f, 1.0f, 1.0f, 1.0f };
	GLfloat light_diffuse[]= { 1.0f, 1.0f, 1.0f, 1.0f };
	GLfloat light_specular[]= { 1.0f, 1.0f, 1.0f, 1.0f };
	glLightfv (GL_LIGHT1, GL_AMBIENT, light_ambient);
	glLightfv (GL_LIGHT1, GL_DIFFUSE, light_diffuse);
	glLightfv (GL_LIGHT1, GL_SPECULAR, light_specular);
	glLightfv(GL_LIGHT1, GL_POSITION, LightPosition);	// Set Up Light Position
	glEnable (GL_LIGHT1);
	glEnable (GL_LIGHTING);

	
	// Beweeg camera
	if( !m_cameraMovementMap.moveCamera){
		if( m_cameraMovementMap.forward )
			m_cameraPos.z -= 80 * frametime;

		if( m_cameraMovementMap.backward )
			m_cameraPos.z += 80 * frametime;

		if( m_cameraMovementMap.left )
			m_cameraPos.x -= 80 * frametime;

		if( m_cameraMovementMap.right )
			m_cameraPos.x += 80 * frametime;

		if( m_cameraMovementMap.up )
			m_cameraPos.y += 80 * frametime;

		if( m_cameraMovementMap.down )
			m_cameraPos.y -= 80 * frametime;

		if( m_cameraMovementMap.rotateup )
			m_cameraAngle.x += 50 * frametime;

		if( m_cameraMovementMap.rotatedown )
			m_cameraAngle.x -= 50 * frametime;

		if( m_cameraMovementMap.rotateleft )
			m_cameraAngle.y += 50 * frametime;

		if( m_cameraMovementMap.rotateright )
			m_cameraAngle.y -= 50 * frametime;

		if( m_cameraAngle.x > 360 )
			m_cameraAngle.x = 0;

		if( m_cameraAngle.x < 0 )
			m_cameraAngle.x = 360;

		if( m_cameraAngle.y >360)
			m_cameraAngle.y =0;

		if( m_cameraAngle.y < 0)
			m_cameraAngle.y =360;

		glTranslatef( -m_cameraPos.x, -m_cameraPos.y, -m_cameraPos.z );
		glRotatef((int)m_cameraAngle.x, 1.0f, 0.0f, 0.0f); //Rotate the camera (up /down)
		glRotatef((int)m_cameraAngle.y, 0.0f, 1.0f, 0.0f); //Rotate the camera (left/rigth)
	}else{
		Player * player = m_game->getScene()->getPlayer();
		m_cameraPos = player->getAbsolutePosition();
		m_cameraPos.y += 30;
		//m_cameraPos.z -= 50;
	
		m_cameraAngle.y = m_game->getScene()->getPlayer()->getRotation().y;
		m_cameraAngle.y*=-1;
		m_cameraAngle.x = 45;

		glTranslatef(0,0,-50);
		glRotatef((int)m_cameraAngle.x, 1.0f, 0.0f, 0.0f); //Rotate the camera (up /down)
		glRotatef((int)m_cameraAngle.y, 0.0f, 1.0f, 0.0f); //Rotate the camera (left/rigth)
		glRotatef((int)m_cameraAngle.z, 0.0f, 0.0f, 1.0f); //Rotate the camera (z as)
		glTranslatef( -m_cameraPos.x, -m_cameraPos.y, -m_cameraPos.z );
		
	}
	
	calculateFrustumPoints();

	// Draw world
	m_RenderVisitor->visit(  m_game->getScene()->getWorldEntity() );

	m_fps = (unsigned long)(1.f/frametime);

	//hud tekenen
	ortho();
	drawHud();
	perspective();		

	// Swap buffers
	glFlush();
	SDL_GL_SwapBuffers();
}

void Camera::drawText( const Position& h, char *string)
{
	glColor3f(1,1,1);
	int len, i;
	glRasterPos2f(h.x, h.y);
	len = (int) strlen(string);
	for (i = 0; i < len; i++)
	{
		glutBitmapCharacter(GLUT_BITMAP_HELVETICA_18, string[i]);
	}
}

void Camera::drawBoundingBox( AbstractEntity* ent )
{
	// Probeer niet wereld entity te tekenen
	if( ent == m_game->getScene()->getWorldEntity() )
		return;

	glPolygonMode(GL_FRONT,GL_LINE); // Laat lijntjes zien
	glPolygonMode(GL_BACK,GL_LINE); // Laat lijntjes zien
	glDisable(GL_CULL_FACE);

	if( !ent->isGroup() )
		glColor4f(1.0f,1.0f,1.0f, 0.7f); // Doorzichtig wit
	else
		glColor4f(1.0f,0.0f,0.0f, 0.7f); // Doorzichtig rood

	Cube cube = Cube(ent->getBoundingSize().x*2, ent->getBoundingSize().y*2, ent->getBoundingSize().z*2);
	std::vector<Position>::const_iterator it3;
	
	glBegin(GL_TRIANGLES);
	for( it3 = cube.getVertexes().begin(); it3 != cube.getVertexes().end(); it3++ )
	{
		const Position& p = ent->getAbsolutePosition() + *it3;
		glVertex3f( p.x, p.y, p.z );
	}
	glEnd();

	// Resetten
	glPolygonMode(GL_FRONT,GL_FILL);
	glPolygonMode(GL_BACK,GL_FILL);
	glEnable(GL_CULL_FACE);
}

void Camera::setMovementMap( const CameraMovementMap& map )
{
	m_cameraMovementMap = map;
}

void Camera::calculateFrustumPoints()
{
	float   proj[16];
	float   modl[16];
	float   clip[16];
	float   t;

	/* Get the current PROJECTION matrix from OpenGL */
	glGetFloatv( GL_PROJECTION_MATRIX, proj );

	/* Get the current MODELVIEW matrix from OpenGL */
	glGetFloatv( GL_MODELVIEW_MATRIX, modl );

	/* Combine the two matrices (multiply projection by modelview) */
	clip[ 0] = modl[ 0] * proj[ 0] + modl[ 1] * proj[ 4] + modl[ 2] * proj[ 8] + modl[ 3] * proj[12];
	clip[ 1] = modl[ 0] * proj[ 1] + modl[ 1] * proj[ 5] + modl[ 2] * proj[ 9] + modl[ 3] * proj[13];
	clip[ 2] = modl[ 0] * proj[ 2] + modl[ 1] * proj[ 6] + modl[ 2] * proj[10] + modl[ 3] * proj[14];
	clip[ 3] = modl[ 0] * proj[ 3] + modl[ 1] * proj[ 7] + modl[ 2] * proj[11] + modl[ 3] * proj[15];

	clip[ 4] = modl[ 4] * proj[ 0] + modl[ 5] * proj[ 4] + modl[ 6] * proj[ 8] + modl[ 7] * proj[12];
	clip[ 5] = modl[ 4] * proj[ 1] + modl[ 5] * proj[ 5] + modl[ 6] * proj[ 9] + modl[ 7] * proj[13];
	clip[ 6] = modl[ 4] * proj[ 2] + modl[ 5] * proj[ 6] + modl[ 6] * proj[10] + modl[ 7] * proj[14];
	clip[ 7] = modl[ 4] * proj[ 3] + modl[ 5] * proj[ 7] + modl[ 6] * proj[11] + modl[ 7] * proj[15];

	clip[ 8] = modl[ 8] * proj[ 0] + modl[ 9] * proj[ 4] + modl[10] * proj[ 8] + modl[11] * proj[12];
	clip[ 9] = modl[ 8] * proj[ 1] + modl[ 9] * proj[ 5] + modl[10] * proj[ 9] + modl[11] * proj[13];
	clip[10] = modl[ 8] * proj[ 2] + modl[ 9] * proj[ 6] + modl[10] * proj[10] + modl[11] * proj[14];
	clip[11] = modl[ 8] * proj[ 3] + modl[ 9] * proj[ 7] + modl[10] * proj[11] + modl[11] * proj[15];

	clip[12] = modl[12] * proj[ 0] + modl[13] * proj[ 4] + modl[14] * proj[ 8] + modl[15] * proj[12];
	clip[13] = modl[12] * proj[ 1] + modl[13] * proj[ 5] + modl[14] * proj[ 9] + modl[15] * proj[13];
	clip[14] = modl[12] * proj[ 2] + modl[13] * proj[ 6] + modl[14] * proj[10] + modl[15] * proj[14];
	clip[15] = modl[12] * proj[ 3] + modl[13] * proj[ 7] + modl[14] * proj[11] + modl[15] * proj[15];

	/* Extract the numbers for the RIGHT plane */
	m_frustum[0][0] = clip[ 3] - clip[ 0];
	m_frustum[0][1] = clip[ 7] - clip[ 4];
	m_frustum[0][2] = clip[11] - clip[ 8];
	m_frustum[0][3] = clip[15] - clip[12];

	/* Normalize the result */
	t = sqrt( m_frustum[0][0] * m_frustum[0][0] + m_frustum[0][1] * m_frustum[0][1] + m_frustum[0][2] * m_frustum[0][2] );
	m_frustum[0][0] /= t;
	m_frustum[0][1] /= t;
	m_frustum[0][2] /= t;
	m_frustum[0][3] /= t;

	/* Extract the numbers for the LEFT plane */
	m_frustum[1][0] = clip[ 3] + clip[ 0];
	m_frustum[1][1] = clip[ 7] + clip[ 4];
	m_frustum[1][2] = clip[11] + clip[ 8];
	m_frustum[1][3] = clip[15] + clip[12];

	/* Normalize the result */
	t = sqrt( m_frustum[1][0] * m_frustum[1][0] + m_frustum[1][1] * m_frustum[1][1] + m_frustum[1][2] * m_frustum[1][2] );
	m_frustum[1][0] /= t;
	m_frustum[1][1] /= t;
	m_frustum[1][2] /= t;
	m_frustum[1][3] /= t;

	/* Extract the BOTTOM plane */
	m_frustum[2][0] = clip[ 3] + clip[ 1];
	m_frustum[2][1] = clip[ 7] + clip[ 5];
	m_frustum[2][2] = clip[11] + clip[ 9];
	m_frustum[2][3] = clip[15] + clip[13];

	/* Normalize the result */
	t = sqrt( m_frustum[2][0] * m_frustum[2][0] + m_frustum[2][1] * m_frustum[2][1] + m_frustum[2][2] * m_frustum[2][2] );
	m_frustum[2][0] /= t;
	m_frustum[2][1] /= t;
	m_frustum[2][2] /= t;
	m_frustum[2][3] /= t;

	/* Extract the TOP plane */
	m_frustum[3][0] = clip[ 3] - clip[ 1];
	m_frustum[3][1] = clip[ 7] - clip[ 5];
	m_frustum[3][2] = clip[11] - clip[ 9];
	m_frustum[3][3] = clip[15] - clip[13];

	/* Normalize the result */
	t = sqrt( m_frustum[3][0] * m_frustum[3][0] + m_frustum[3][1] * m_frustum[3][1] + m_frustum[3][2] * m_frustum[3][2] );
	m_frustum[3][0] /= t;
	m_frustum[3][1] /= t;
	m_frustum[3][2] /= t;
	m_frustum[3][3] /= t;

	/* Extract the FAR plane */
	m_frustum[4][0] = clip[ 3] - clip[ 2];
	m_frustum[4][1] = clip[ 7] - clip[ 6];
	m_frustum[4][2] = clip[11] - clip[10];
	m_frustum[4][3] = clip[15] - clip[14];

	/* Normalize the result */
	t = sqrt( m_frustum[4][0] * m_frustum[4][0] + m_frustum[4][1] * m_frustum[4][1] + m_frustum[4][2] * m_frustum[4][2] );
	m_frustum[4][0] /= t;
	m_frustum[4][1] /= t;
	m_frustum[4][2] /= t;
	m_frustum[4][3] /= t;

	/* Extract the NEAR plane */
	m_frustum[5][0] = clip[ 3] + clip[ 2];
	m_frustum[5][1] = clip[ 7] + clip[ 6];
	m_frustum[5][2] = clip[11] + clip[10];
	m_frustum[5][3] = clip[15] + clip[14];

	/* Normalize the result */
	t = sqrt( m_frustum[5][0] * m_frustum[5][0] + m_frustum[5][1] * m_frustum[5][1] + m_frustum[5][2] * m_frustum[5][2] );
	m_frustum[5][0] /= t;
	m_frustum[5][1] /= t;
	m_frustum[5][2] /= t;
	m_frustum[5][3] /= t;

	for(int i = 0; i < 6; i++){
	//m_frustum[i][0] += m_cameraPos.x;
	//m_frustum[i][1] += m_cameraPos.y;
	//m_frustum[i][2] += m_cameraPos.z;
		Vector temp;
		temp.x = m_frustum[i][0];
		temp.y = m_frustum[i][1];
		temp.z = m_frustum[i][2];
		//Transformation::apply(Transformation::getTranslationMatrix(m_cameraPos * -1), temp);
		//Transformation::apply(Transformation::getRotationMatrix(m_cameraAngle * -1), temp);
		//Transformation::apply(Transformation::getTranslationMatrix(m_cameraPos * 1), temp);

		m_frustum[i][0] = temp.x;
		m_frustum[i][1] = temp.y;
		m_frustum[i][2] = temp.z; 
	}
}

bool Camera::isBoundingBoxInFrustum( Vector vec, Vector size )
{
	float x = vec.x;
	float y = vec.y;
	float z = vec.z;

	int p;

	for( p = 0; p < 6; p++ )
	{
		if( m_frustum[p][0] * (x - size.x) + m_frustum[p][1] * (y - size.y) + m_frustum[p][2] * (z - size.z) + m_frustum[p][3] > 0 )
			continue;
		if( m_frustum[p][0] * (x + size.x) + m_frustum[p][1] * (y - size.y) + m_frustum[p][2] * (z - size.z) + m_frustum[p][3] > 0 )
			continue;
		if( m_frustum[p][0] * (x - size.x) + m_frustum[p][1] * (y + size.y) + m_frustum[p][2] * (z - size.z) + m_frustum[p][3] > 0 )
			continue;
		if( m_frustum[p][0] * (x + size.x) + m_frustum[p][1] * (y + size.y) + m_frustum[p][2] * (z - size.z) + m_frustum[p][3] > 0 )
			continue;
		if( m_frustum[p][0] * (x - size.x) + m_frustum[p][1] * (y - size.y) + m_frustum[p][2] * (z + size.z) + m_frustum[p][3] > 0 )
			continue;
		if( m_frustum[p][0] * (x + size.x) + m_frustum[p][1] * (y - size.y) + m_frustum[p][2] * (z + size.z) + m_frustum[p][3] > 0 )
			continue;
		if( m_frustum[p][0] * (x - size.x) + m_frustum[p][1] * (y + size.y) + m_frustum[p][2] * (z + size.z) + m_frustum[p][3] > 0 )
			continue;
		if( m_frustum[p][0] * (x + size.x) + m_frustum[p][1] * (y + size.y) + m_frustum[p][2] * (z + size.z) + m_frustum[p][3] > 0 )
			continue;
		return false;
	}
	return true;
}

void Camera::drawNormals( Entity* ent )
{
	glColor3f(1,1,1);
	
	std::vector<Vector>::const_iterator it = ent->getShape()->getFaces().begin();
	for( ; it != ent->getShape()->getFaces().end(); it++ )
	{
		glBegin(GL_LINES);

		Vector normaal = (*it).normalize();

		Vector start = normaal + ent->getAbsolutePosition();

		glVertex3f( start.x, start.y, start.z );

		Vector end = ent->getAbsolutePosition() + normaal * 25;

		glVertex3f( end.x, end.y, end.z );
		glEnd();
	}
}

void Camera::ortho( void )								// Changes Projection To Orthgonal View For Text
{
	glDisable(GL_DEPTH_TEST);							// Disable Depth Testing (All Text On Top!)
	glDisable(GL_LIGHTING);								// Disable Lighting
	glMatrixMode(GL_PROJECTION);						// Save The Current PROJECTION Matrix
	glPushMatrix();
	glLoadIdentity();
	glOrtho( 0, m_width, m_height, 0, -1, 1 );	// Enter Orthographic Mode
	glMatrixMode(GL_MODELVIEW);							// Save The MODELVIEW Matrix
	glPushMatrix();
	glLoadIdentity();
}

void Camera::perspective( void )						// Changes Projection To Perspective View
{
	glMatrixMode( GL_PROJECTION );						// Restore The PROJECTION Matrix
	glPopMatrix();
	glMatrixMode( GL_MODELVIEW );						// Restore The MODELVIEW Matrix
	glPopMatrix();
	glEnable( GL_DEPTH_TEST );							// Enable Depth Testing
	glEnable(GL_LIGHTING);								// Enable Lighting
}

void Camera::drawHud()
{
	glColor3f(1, 1, 1);

	// FPS
	char buf[128];
	sprintf( buf, "FPS: %ld", m_fps );
	drawText((Position(m_width-80, 20, 0)), buf);

	// Aantal objecten in frustum
	sprintf( buf, "Frustum: %d objects", MAX(m_RenderVisitor->getEntitiesVisited()-1, 0) );
	drawText((Position(m_width-170, m_height-20, 0)), buf);
}